#region Using

using System;
using System.IO;
using System.Web;
using System.Net;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Xml;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using BlogEngine.Core;

#endregion


namespace Controls
{
    /// <summary>
    /// Creates and displays a dynamic blogroll.
    /// </summary>
    public class Blogroll : Control
    {
        static Blogroll()
        {
            BlogSettings.Changed += new EventHandler<EventArgs>(BlogSettings_Changed);
            Link.Saved += new EventHandler<SavedEventArgs>(Link_Saved);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (!Page.IsPostBack && !Page.IsCallback)
            {
                HtmlGenericControl ul = DisplayBlogroll();
                StringWriter sw = new StringWriter();
                ul.RenderControl(new HtmlTextWriter(sw));
                string html = sw.ToString();

                writer.WriteLine("<div id=\"blogroll\">");
                writer.WriteLine(html);
                writer.WriteLine("</div>");
            }
        }

        private static void BlogSettings_Changed(object sender, EventArgs e)
        {
            Update();
        }

        private static void Link_Saved(object sender, SavedEventArgs e)
        {
            Update();
        }


        #region Private fields

        private static Collection<RssItem> _Items;
        private static DateTime _LastUpdated = DateTime.Now;

        #endregion

        #region Methods

        public static void Update()
        {
            _Items = null;
        }

        private static object _SyncRoot = new object();

        /// <summary>
        /// Displays the RSS item collection.
        /// </summary>
        private HtmlGenericControl DisplayBlogroll()
        {
            if (DateTime.Now > _LastUpdated.AddMinutes(BlogSettings.Instance.BlogrollUpdateMinutes) && BlogSettings.Instance.BlogrollVisiblePosts > 0)
            {
                _Items = null;
                _LastUpdated = DateTime.Now;
            }

            if (_Items == null)
            {
                lock (_SyncRoot)
                {
                    if (_Items == null)
                    {
                        _Items = new Collection<RssItem>();
                        AddBlog();
                    }
                }
            }

            return BindControls();
        }


        /// <summary>
        /// Parses the processed RSS items and returns the HTML
        /// </summary>
        private HtmlGenericControl BindControls()
        {
            HtmlGenericControl ul = new HtmlGenericControl("ul");
            ul.Attributes.Add("class", "oxox");
            foreach (RssItem item in _Items)
            {
                HtmlGenericControl li = new HtmlGenericControl("li");

                AddFeedAnchor(item, li);
                AddWebAnchor(item, li);
                AddRssChildItems(item, li);
                ul.Controls.Add(li);
            }

            return ul;
        }

        private void AddFeedAnchor(RssItem item, HtmlGenericControl li)
        {
            if (!string.IsNullOrEmpty(item.RssUrl))
            {
                HtmlAnchor feedAnchor = new HtmlAnchor();
                feedAnchor.HRef = item.RssUrl;

                HtmlImage image = new HtmlImage();
                image.Src = Utils.RelativeWebRoot + "pics/rssButton.gif";
                image.Alt = "RSS feed for " + item.Name;

                feedAnchor.Controls.Add(image);

                li.Controls.Add(feedAnchor);
            }
        }

        private void AddWebAnchor(RssItem item, HtmlGenericControl li)
        {
            HtmlAnchor webAnchor = new HtmlAnchor();
            webAnchor.HRef = item.WebsiteUrl;
            webAnchor.InnerHtml = EnsureLength(item.Name);

            if (!String.IsNullOrEmpty(item.Xfn))
                webAnchor.Attributes["rel"] = item.Xfn;


            li.Controls.Add(webAnchor);
        }

        private void AddRssChildItems(RssItem item, HtmlGenericControl li)
        {
            if (item.Items.Count > 0 && BlogSettings.Instance.BlogrollVisiblePosts > 0)
            {
                HtmlGenericControl div = new HtmlGenericControl("ul");
                int i = 0;
                foreach (string key in item.Items.Keys)
                {
                    if (i >= BlogSettings.Instance.BlogrollVisiblePosts) break;

                    HtmlGenericControl subLi = new HtmlGenericControl("li");
                    HtmlAnchor a = new HtmlAnchor();
                    a.HRef = item.Items[key];
                    a.Title = HttpUtility.HtmlEncode(key);
                    a.InnerHtml = EnsureLength(key);

                    subLi.Controls.Add(a);
                    div.Controls.Add(subLi);
                    i++;
                }

                li.Controls.Add(div);
            }
        }

        /// <summary>
        /// Ensures that the name is no longer than the MaxLength.
        /// </summary>
        private string EnsureLength(string textToShorten)
        {
            if (textToShorten.Length > BlogSettings.Instance.BlogrollMaxLength)
                return textToShorten.Substring(0, BlogSettings.Instance.BlogrollMaxLength).Trim() + "...";

            return HttpUtility.HtmlEncode(textToShorten);
        }

        /// <summary>
        /// Adds a blog to the item collection and start retrieving the blogs.
        /// </summary>
        private static void AddBlog()
        {
            foreach (Link item in Link.Items)
            {
                if (item.IsPublished && item.BlogRoll)
                {
                    RssItem blog = new RssItem();
                    blog.RssUrl = item.Feed;
                    blog.WebsiteUrl = item.Website;
                    blog.Name = item.Title;
                    blog.Description = item.Description;
                    blog.Xfn = item.XFNTag;

                    //only try to get the latest feed information if a feed url is specified.
                    if (!string.IsNullOrEmpty(item.Feed))
                    {
                        blog.Request = (HttpWebRequest)WebRequest.Create(item.Feed);
                        blog.Request.Credentials = CredentialCache.DefaultNetworkCredentials;

                        _Items.Add(blog);

                        blog.Request.BeginGetResponse(ProcessRespose, blog);
                    }
                    else
                    {
                        _Items.Add(blog);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the request and processes the response.
        /// </summary>
        private static void ProcessRespose(IAsyncResult async)
        {
            RssItem item = (RssItem)async.AsyncState;
            try
            {
                using (HttpWebResponse response = (HttpWebResponse)item.Request.EndGetResponse(async))
                {
                    XmlDocument doc = new XmlDocument();
                    doc.Load(response.GetResponseStream());

                    XmlNodeList nodes = doc.SelectNodes("rss/channel/item");
                    foreach (XmlNode node in nodes)
                    {
                        string title = node.SelectSingleNode("title").InnerText;
                        string link = node.SelectSingleNode("link").InnerText;
                        DateTime date = DateTime.Now;
                        if (node.SelectSingleNode("pubDate") != null)
                            date = DateTime.Parse(node.SelectSingleNode("pubDate").InnerText);

                        item.Items.Add(title, link);
                    }
                }
            }
            catch
            { }
        }

        #endregion

        #region RssItem class

        /// <summary>
        /// The RSS items used to display on the blogroll.
        /// </summary>
        private class RssItem
        {
            public HttpWebRequest Request;
            public string RssUrl;
            public string WebsiteUrl;
            public string Name;
            public string Description;
            public string Xfn;
            public Dictionary<string, string> Items = new Dictionary<string, string>();
        }

        #endregion

    }
}

public static class Updater
{
    public static void UpdateBlogroll()
    {
        Controls.Blogroll.Update();
    }
}